package com.naiterui.ehp.bs.patient.service.impl;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.google.common.collect.ImmutableMap;
import com.naiterui.ehp.bp.domain.Patient;
import com.naiterui.ehp.bp.support.exception.BusinessException;
import com.naiterui.ehp.bp.support.exception.JsonException;
import com.naiterui.ehp.bp.support.utils.JsonMapper;
import com.naiterui.ehp.bp.utils.date.DateUtil;
import com.naiterui.ehp.bs.patient.dao.IMedicationRemindDao;
import com.naiterui.ehp.bs.patient.domain.MedicationRemind;
import com.naiterui.ehp.bs.patient.exception.ExceptionCodes;
import com.naiterui.ehp.bs.patient.service.IMedicationRemindService;
import com.naiterui.ehp.bs.patient.service.IPatientService;
import com.naiterui.ehp.bs.patient.utils.ConfigKeyUtil;
import com.naiterui.ehp.bs.patient.utils.exception.PatientBusinessException;
import com.naiterui.ehp.bs.patient.vo.MedicationRemindVO;

/**
 * @author fanjd
 * @since 1.0.0
 */
@Service
public class ApMedicationRemindServiceImpl implements IMedicationRemindService {
    private static final Logger LOGGER = LoggerFactory.getLogger(ApMedicationRemindServiceImpl.class);
    /**
     * 患者开启
     **/
    private static final Integer REMIND_TYPE_PATIENT = 1;
    /**
     * 提醒次数-1次
     **/
    private static final Integer REMIND_TIMES_ONE = 1;
    /**
     * 提醒次数-2次
     **/
    private static final Integer REMIND_TIMES_SECOND = 2;
    /**
     * 提醒次数-3次
     **/
    private static final Integer REMIND_TIMES_THIRD = 3;
    /**
     * 默认提醒天数
     **/
    private static final Integer DEFAULT_REMIND_DAYS = 7;
    /**
     * 开启状态默认关闭
     **/
    private static final Integer DEFAULT_REMIND_OPENDED = 0;
    /**
     * 开启状态为开启
     **/
    private static final Integer REMIND_OPENDED_TRUE = 1;
    /**
     * 最长提醒的天数
     **/
    private static final Integer REMIND_MAX_DAYS = 30;
    /**
     * 使用微信固定key
     **/
    private static final String WX_TYPE = "javamedicine";
    /**
     * 推荐未成单使用微信固定key
     **/
    private static final String WX_KEY = "medicine";
    /**
     * 用药提醒timeOutStatus
     **/
    private static final String TIME_OUT_STATUS = "4";
    /**
     * 重试次数
     */
    private static final Integer MAX_RETRY_TIMES = 3;
    /**
     * 等待重试时间
     */
    private static final Long WAIT_FOR_RETRY_TIME = 1000 * 10L;
    /**
     * 第一次提醒时间-8:00:00
     **/
    private static String MEDICATION_REMIND_TIME_FIRST = ConfigKeyUtil.getString("medication.remind_time_first");
    /**
     * 第二次提醒时间-15:00:00
     **/
    private static String MEDICATION_REMIND_TIME_SECOND = ConfigKeyUtil.getString("medication.remind_time_second");
    /**
     * 第三次提醒时间-22:00:00
     **/
    private static String MEDICATION_REMIND_TIME_THIRD = ConfigKeyUtil.getString("medication.remind_time_third");
    /**
     * 初始化小时和对应的标签关系
     **/
    private static final Map<String, Integer> REMIND_HOUR_LABEL_MAP = ImmutableMap.of("8", 1, "15", 2, "22", 4);

    @Autowired
    private IMedicationRemindDao medicationRemindDao;
    @Autowired
    private IPatientService patientService;

    /**
     * 用药提醒详情
     *
     * @param patientId
     *
     * @return
     *
     * @throws BusinessException
     * @Author fanjd
     * @Date 2018年1月18日
     * @see com.naiterui.ehp.bs.patient.service.IMedicationRemindService#detail(java.lang.Long)
     * @since 1.0.0
     */
    @Override
    public MedicationRemindVO detail(Long patientId) throws BusinessException {
        // 校验患者
        this.patientValidation(patientId);
        MedicationRemind medicationRemind = this.medicationRemindDao.getMedicationRemind(patientId);
        Long id = null;
        // 默认提醒一次
        Integer remindTimes = REMIND_TIMES_ONE;
        // 提醒时间文案
        List<String> remindMsg = new ArrayList<>();
        // 提醒天数
        Integer remindDays = DEFAULT_REMIND_DAYS;
        // 是否开启 0--未开启 1-已开启
        Integer opened = DEFAULT_REMIND_OPENDED;
        // 没有记录或者已经过期给默认值
        if (null == medicationRemind || DateUtil.compareDate(new Date(), medicationRemind.getExpireAt()) > 0) {
            remindMsg.add(String.format("上午%s提醒一次", MEDICATION_REMIND_TIME_FIRST));
        } else {
            if (medicationRemind.validateLabel(MedicationRemind.LABLE_FIRST_REMIND)) {
                remindMsg.add(String.format("上午%s提醒一次", MEDICATION_REMIND_TIME_FIRST));
            }
            if (medicationRemind.validateLabel(MedicationRemind.LABLE_SECOND_REMIND)) {
                remindMsg.add(String.format("下午%s提醒一次", MEDICATION_REMIND_TIME_SECOND));
            }
            if (medicationRemind.validateLabel(MedicationRemind.LABLE_THIRD_REMIND)) {
                remindMsg.add(String.format("晚上%s提醒一次", MEDICATION_REMIND_TIME_THIRD));
            }

            id = medicationRemind.getId();
            remindTimes = medicationRemind.getTimes();
            remindDays = this.daysOfTwo(medicationRemind.getExpireAt(), new Date());
            opened = DateUtil.compareDate(new Date(), medicationRemind.getExpireAt()) > 0 ? DEFAULT_REMIND_OPENDED : REMIND_OPENDED_TRUE;
        }
        MedicationRemindVO medicationRemindVO = new MedicationRemindVO(id, remindTimes, remindMsg, remindDays, opened);
        return medicationRemindVO;
    }

    /**
     * 新增/更新用药提醒
     *
     * @param patientId
     * @param remindTimes   提醒次数
     * @param remindDays    提醒天数
     * @param remindContent 提醒内容
     * @param type          操作类型 0--系统1--患者
     *
     * @throws BusinessException
     * @Author fanjd
     * @Date 2018年1月18日
     * @see com.naiterui.ehp.bs.patient.service.IMedicationRemindService#update(java.lang.Long,
     * java.lang.Integer, java.lang.Integer, java.lang.String, java.lang.Integer)
     * @since 1.0.0
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(Long patientId, Integer remindTimes, Integer remindDays, String remindContent, Integer type) throws BusinessException {
        // 校验天数
        this.parameterValidation(patientId, remindTimes, remindDays);
        // 校验患者
        this.patientValidation(patientId);
        MedicationRemind medicationRemind = this.medicationRemindDao.getMedicationRemind(patientId);
        Map<String, Object> map = new HashMap<>(4);
        Integer openType = type == null ? REMIND_TYPE_PATIENT : type;
        String proson = type == null ? patientId + "" : "system";
        if (null == medicationRemind) {
            // 关闭状态下,提醒天数和提醒次数不允许为空，如果有一个出现空，即非正常调用
            boolean flag = null == remindDays || null == remindTimes;
            if (flag) {
                LOGGER.error("系统出现了非正常调用，患者id:{}，remindDays:{},remindTime:{}", patientId, remindDays, remindTimes);
                return;
            }
            // 患者开启
            if (StringUtils.isBlank(remindContent)) {
                remindContent = this.buildDefaultContext();
            }
            // 构建过期时间和提醒时间二进制标签
            map = this.handleExpireAtAndLabelType(remindTimes, remindDays, true);
            Integer labelType = (Integer) map.get("labelType");
            Date expireAt = (Date) map.get("expireAt");
            medicationRemind = new MedicationRemind(patientId, labelType, remindTimes, openType, remindContent, expireAt, new Date(), proson, proson,
                                                    new Date());

        } else {
            // 判断是否过期
            long num = DateUtil.compareDate(new Date(), medicationRemind.getExpireAt());
            // 如果提醒过期,那么提醒天数和提醒次数不允许为空，如果有一个出现空，即非正常调用
            boolean flag = num > 0 && (null == remindDays || null == remindTimes);
            if (flag) {
                LOGGER.error("系统出现了非正常调用，患者id:{}", patientId);
                return;
            } else {
                Date expireAt = null;
                // 单独更新天数
                if (null != remindDays && remindTimes == null) {
                    Calendar c = Calendar.getInstance();
                    c.setTime(medicationRemind.getExpireAt());
                    Integer hour = c.get(Calendar.HOUR_OF_DAY);
                    // 构建最后一天的提示小时
                    expireAt = this.getSpecifiedTime(hour + ":00", remindDays);

                }
                // 单独更新次数
                if (remindTimes != null && null == remindDays) {
                    Integer days = this.daysOfTwo(medicationRemind.getExpireAt(), new Date());
                    map = this.handleExpireAtAndLabelType(remindTimes, days, false);
                    Integer labelType = (Integer) map.get("labelType");
                    expireAt = (Date) map.get("expireAt");
                    // 更新提醒时间二进制标签值
                    medicationRemind.setLabelType(labelType);
                    // 更新提醒次数
                    medicationRemind.setTimes(remindTimes);
                }
                // 更新时间和次数
                if (remindTimes != null && null != remindDays) {
                    map = this.handleExpireAtAndLabelType(remindTimes, remindDays, true);
                    Integer labelType = (Integer) map.get("labelType");
                    expireAt = (Date) map.get("expireAt");
                    // 更新提醒时间二进制标签值
                    medicationRemind.setLabelType(labelType);
                    // 更新提醒次数
                    medicationRemind.setTimes(remindTimes);
                }
                medicationRemind.setType(openType);
                // 过期时间
                medicationRemind.setExpireAt(expireAt);
                medicationRemind.setChangedAt(new Date());
                medicationRemind.setChangedBy(proson);
                if (StringUtils.isBlank(remindContent)) {
                    // 患者修改提醒模版变成默认模版
                    remindContent = this.buildDefaultContext();
                }
                medicationRemind.setRemindContent(remindContent);

            }
        }
        this.medicationRemindDao.saveOrUpdate(medicationRemind);
    }

    /**
     * 构建默认提醒内容
     *
     * @return
     *
     * @Author fanjd
     * @Date 2018年1月24日
     * @since 2.14
     */
    private String buildDefaultContext() {
        String remindContent = "";
        // 药品名默认值
        String drugName = "具体以医生处方为准 ";
        // 用法用量默认值
        String usages = "请遵医嘱";
        // 构建商品名、用法用量的集合
        Map<String, Object> recomMap = new HashMap<>(4);
        recomMap.put("name", drugName);
        recomMap.put("usages", usages);
        try {
            remindContent = JsonMapper.toJson(recomMap);
        } catch (JsonException e) {
            LOGGER.error("药品名和用法用量map转json出现了异常，异常信息:{},堆栈信息:{}", e.getMessage(), e);
        }
        return remindContent;
    }

    /**
     * 多个时间和当前时间比较，有一个时间比当前时间大，即返回true
     * MedicationRemindServiceImpl.compareListDate()
     *
     * @param remindList
     *
     * @return
     *
     * @Author fanjd
     * @Date 2018年1月4日
     * @since 1.0.0
     */
    private Boolean compareListDate(List<String> remindList) {
        for (String remindTime : remindList) {
            Boolean flag = this.compareDate(remindTime);
            if (flag) {
                return true;
            }
        }
        return false;
    }

    /**
     * 一个时间和当前时间做比较
     *
     * @param remindTime 9:00:00/12:00:00/19:00:00
     *
     * @return 当前时间比提醒时间小 返回true 否则返回false
     *
     * @Author fanjd
     * @Date 2018年1月3日
     * @since 1.0.0
     */
    private Boolean compareDate(String remindTime) {
        // 提醒时间
        Date date = DateUtil.getHourDate(remindTime);
        long num = DateUtil.compareDate(new Date(), date);
        if (num < 0) {
            return true;
        }
        return false;
    }

    /**
     * 根据提醒次数和提醒天数计算过期时间和提醒时间二进制标签值
     *
     * @param remindTimes 提醒次数
     * @param remindDays  提醒天数
     *
     * @return
     *
     * @Author fanjd
     * @Date 2018年1月18日
     * @since 1.0.0
     */
    private Map<String, Object> handleExpireAtAndLabelType(Integer remindTimes, Integer remindDays, Boolean opened) {
        // 默认中午12点一次
        Integer labelType = MedicationRemind.LABLE_FIRST_REMIND;
        Map<String, Object> map = new HashMap<>(4);
        // 需要提醒的时间集合
        List<String> remindList = new ArrayList<>();
        String remindStr = "";
        if (REMIND_TIMES_SECOND.equals(remindTimes)) {
            // 2次的标签值（8点和22点）
            labelType = MedicationRemind.LABLE_FIRST_REMIND + MedicationRemind.LABLE_THIRD_REMIND;
            // 关闭状态提醒天数才根具当天剩余提醒次数关联
            if (opened) {
                // 8点
                remindList.add(MEDICATION_REMIND_TIME_FIRST);
                // 22点
                remindList.add(MEDICATION_REMIND_TIME_THIRD);
                if (this.compareListDate(remindList)) {
                    remindDays = remindDays - 1;
                }
            }

            remindStr = MEDICATION_REMIND_TIME_THIRD;
        } else if (REMIND_TIMES_THIRD.equals(remindTimes)) {
            // 三次的标签值
            labelType = MedicationRemind.LABLE_FIRST_REMIND + MedicationRemind.LABLE_THIRD_REMIND + MedicationRemind.LABLE_SECOND_REMIND;
            if (opened) {
                // 8点
                remindList.add(MEDICATION_REMIND_TIME_FIRST);
                // 15点
                remindList.add(MEDICATION_REMIND_TIME_SECOND);
                // 22点
                remindList.add(MEDICATION_REMIND_TIME_THIRD);
                if (this.compareListDate(remindList)) {
                    remindDays = remindDays - 1;
                }
            }
            remindStr = MEDICATION_REMIND_TIME_THIRD;
            // 计算过期时间
        } else {
            if (opened) {
                // 8点
                remindList.add(MEDICATION_REMIND_TIME_FIRST);
                if (this.compareDate(MEDICATION_REMIND_TIME_FIRST)) {
                    remindDays = remindDays - 1;
                }
            }
            remindStr = MEDICATION_REMIND_TIME_FIRST;
        }
        // 计算过期时间
        Date expireAt = this.getSpecifiedTime(remindStr, remindDays);
        map.put("labelType", labelType);
        map.put("expireAt", expireAt);
        return map;
    }

    /**
     * 计算指定字符串的过期时间
     * MedicationRemindServiceImpl.getExpireAt()
     *
     * @return
     *
     * @Author fanjd
     * @Date 2018年1月3日
     * @since 1.0.0
     */
    private Date getSpecifiedTime(String remindTime, Integer laseDays) {
        // 提醒时间
        Date date = DateUtil.getHourDate(remindTime);
        // 过期时间
        Date expireAt = DateUtil.addDays(date, laseDays);
        return expireAt;
    }

    /**
     * 两个日期相差的天数，不考虑小时
     * MedicationRemindServiceImpl.daysOfTwo()
     *
     * @param startDate 开始日期
     * @param endDate   结束日期
     *
     * @return
     *
     * @Author fanjd
     * @Date 2018年1月4日
     * @since 1.0.0
     */
    private int daysOfTwo(Date endDate, Date startDate) {
        Calendar aCalendar = Calendar.getInstance();
        aCalendar.setTime(startDate);
        int startDay = aCalendar.get(Calendar.DAY_OF_YEAR);
        aCalendar.setTime(endDate);
        int endDay = aCalendar.get(Calendar.DAY_OF_YEAR);
        return endDay - startDay;
    }

    /**
     * 校验患者是否存在
     * MedicationRemindServiceImpl.patientValidation()
     *
     * @param patientId
     *
     * @throws BusinessException
     * @Author fanjd
     * @Date 2018年1月4日
     * @since 1.0.0
     */
    private void patientValidation(Long patientId) throws BusinessException {
        Patient patient = this.patientService.getPatient(patientId);
        // 校验患者
        if (patient == null) {
            LOGGER.error("患者ID:{}不存在", patientId);
            throw new PatientBusinessException(ExceptionCodes.PATIENT_NOT_EXIST);
        }
    }

    private void parameterValidation(Long patientId, Integer remindTimes, Integer remindDays) throws BusinessException {
        if (patientId == null || (remindTimes == null && remindDays == null)) {
            LOGGER.warn("参数校验失败");
            throw new BusinessException(ExceptionCodes.PARAM_ERROR);
        }
        if (remindTimes != null && (remindTimes > REMIND_TIMES_THIRD || remindTimes < REMIND_TIMES_ONE)) {
            LOGGER.warn("提醒次数出现了异常，remindTimes：{}", remindTimes);
            throw new BusinessException(ExceptionCodes.PARAM_ERROR);
        }
        if (remindDays != null && (remindDays > REMIND_MAX_DAYS || remindDays < 0)) {
            LOGGER.warn("提醒天数出现了异常，remindDays：{}", remindDays);
            throw new BusinessException(ExceptionCodes.PARAM_ERROR);
        }
    }

    /**
     * 关闭用药提醒
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void removeMedicationRemind(Long patientId) throws BusinessException {
        // 校验患者
        this.patientValidation(patientId);
        this.medicationRemindDao.removeMedicationRemind(patientId);
    }

}
